﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalR.StockTicker
{
    public class StockTicker
    {
        private static readonly Lazy<StockTicker> _instance = new Lazy<StockTicker>(() =>
            new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

        readonly ConcurrentDictionary<string,Stock> _stocks=new ConcurrentDictionary<string, Stock>();

        readonly object _updateStockPricesLock=new object();

        private readonly double _rangePercent = .002;

        private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
        readonly Random _updateOrNotRandom=new Random();

        private readonly Timer _timer;
        private volatile bool _updatingStockPrices = false;

        private StockTicker(IHubConnectionContext<dynamic> clients)
        {
            Clients = clients;

            this._stocks.Clear();
            var stocks = new List<Stock>()
            {
                new Stock() {Symbol = "MSFT", Price = 30.31m},
                new Stock() {Symbol = "APPL", Price = 578.18m},
                new Stock() {Symbol = "GOOD", Price = 570.30m}
            };
            stocks.ForEach(stock=>this._stocks.TryAdd(stock.Symbol,stock));

            this._timer=new Timer(UpdateStockPrices,null,this._updateInterval,this._updateInterval);
        }

        public static StockTicker Instance => _instance.Value;

        private IHubConnectionContext<dynamic> Clients { get; set; }

        public IEnumerable<Stock> GetAllStocks()
        {
            return this._stocks.Values;
        }

        private void UpdateStockPrices(object state)
        {
            lock (this._updateStockPricesLock)
            {
                if (!this._updatingStockPrices)
                {
                    this._updatingStockPrices = true;
                    foreach (Stock stock in _stocks.Values)
                    {
                        if (TryUpdateStockPrice(stock))
                        {
                            BroadcastStockPrice(stock);
                        }
                    }

                    this._updatingStockPrices = false;
                }
            }
        }

        private void BroadcastStockPrice(Stock stock)
        {
            Clients.All.updateStockPrice(stock);
        }

        private bool TryUpdateStockPrice(Stock stock)
        {
            var r = this._updateOrNotRandom.NextDouble();
            if (r > .1)
            {
                return false;
            }

            var random=new Random((int)Math.Floor(stock.Price));
            var percentChange = random.NextDouble() * this._rangePercent;
            var pos = random.NextDouble() > .51;
            var change = Math.Round(stock.Price * (decimal) percentChange, 2);
            change = pos ? change : -change;

            stock.Price += change;
            return true;
        }
    }
}